//
// PS: Define the fined rectangle receiver data structure.
//
#define SOLAR_BUILD_CUDA_REFINED_RECTANGLE_RECEIVER
#include "CudaRefinedRectangleReceiver.cuh"
#include <algorithm/rayTracing/qmcrt_util/global_constant.h>

#include <stdexcept>

using namespace solar::cuda;

CudaRefinedRectangleReceiver::CudaRefinedRectangleReceiver(
    const CudaRefinedRectangleReceiver& refined_rectangle_receiver)
    : CudaReceiver(refined_rectangle_receiver)
{
    //        for(int i = 0; i < 4; ++i){
    //            rect_vertexes_[i] = rectangle_receiver.getRectangleVertex(i);
    //        }
    rect_vertexes_[0] = refined_rectangle_receiver.getRectangleVertex(0);
    rect_vertexes_[1] = refined_rectangle_receiver.getRectangleVertex(1);
    rect_vertexes_[2] = refined_rectangle_receiver.getRectangleVertex(2);
    rect_vertexes_[3] = refined_rectangle_receiver.getRectangleVertex(3);
    local_normal_ = refined_rectangle_receiver.getLocalNormal();
    radius_ = refined_rectangle_receiver.getRadius();
}

void CudaRefinedRectangleReceiver::CInit(float pixel_per_meter_for_receiver)
{
    pixel_length_ = 1.0F / float(pixel_per_meter_for_receiver);
    Cinit_vertex();
    setFocusCenter();
    Cset_resolution(pixel_per_meter_for_receiver);
    Calloc_image(); // cudaMalloc according to resolution. size = resulotion.x * resolution.y
    Cclean_image_content();
}

auto CudaRefinedRectangleReceiver::getRectangleVertex(int index) const -> float3
{
    if (index > 3)
    {
        throw std::runtime_error("The index should between [0, 3].");
    }
    return rect_vertexes_[index];
}

auto CudaRefinedRectangleReceiver::getLocalNormal() const -> float3 { return local_normal_; }

auto CudaRefinedRectangleReceiver::getFocusCenter(const float3& heliostat_position) -> float3
{
    return focus_center_;
}

void CudaRefinedRectangleReceiver::Cinit_vertex()
{
    Cset_local_normal(); // Set local normal
    Cset_local_vertex(); // Set local vertex according to face type
    Cset_world_vertex(); // Set world vertex according to normal
}

void CudaRefinedRectangleReceiver::Cset_local_normal()
{
    switch (surface_index_)
    {
    case 0:
        local_normal_ = make_float3(0.0F, 0.0F, 1.0F);
        break;
    case 1:
        local_normal_ = make_float3(1.0F, 0.0F, 0.0F);
        break;
    case 2:
        local_normal_ = make_float3(0.0F, 0.0F, -1.0F);
        break;
    case 3:
        local_normal_ = make_float3(-1.0F, 0.0F, 0.0F);
        break;
    case 4:
        local_normal_ = make_float3(0.0F, 1.0F, 0.0F);
        break;
    case 5:
        local_normal_ = make_float3(0.0F, -1.0F, 0.0F);
        break;
    default:
        break;
    }
}

void CudaRefinedRectangleReceiver::Cset_local_vertex()
{
    switch (surface_index_)
    {
    case 0:
        rect_vertexes_[0] = make_float3(-size_.x / 2, -size_.y / 2, size_.z / 2);
        rect_vertexes_[1] = make_float3(-size_.x / 2, size_.y / 2, size_.z / 2);
        rect_vertexes_[2] = make_float3(size_.x / 2, size_.y / 2, size_.z / 2);
        rect_vertexes_[3] = make_float3(size_.x / 2, -size_.y / 2, size_.z / 2);
        break;
    case 1:
        rect_vertexes_[0] = make_float3(size_.x / 2, -size_.y / 2, size_.z / 2);
        rect_vertexes_[1] = make_float3(size_.x / 2, size_.y / 2, size_.z / 2);
        rect_vertexes_[2] = make_float3(size_.x / 2, size_.y / 2, -size_.z / 2);
        rect_vertexes_[3] = make_float3(size_.x / 2, -size_.y / 2, -size_.z / 2);
        break;
    case 2:
        rect_vertexes_[0] = make_float3(size_.x / 2, -size_.y / 2, -size_.z / 2);
        rect_vertexes_[1] = make_float3(size_.x / 2, size_.y / 2, -size_.z / 2);
        rect_vertexes_[2] = make_float3(-size_.x / 2, size_.y / 2, -size_.z / 2);
        rect_vertexes_[3] = make_float3(-size_.x / 2, -size_.y / 2, -size_.z / 2);
        break;
    case 3:
        rect_vertexes_[0] = make_float3(-size_.x / 2, -size_.y / 2, -size_.z / 2);
        rect_vertexes_[1] = make_float3(-size_.x / 2, size_.y / 2, -size_.z / 2);
        rect_vertexes_[2] = make_float3(-size_.x / 2, size_.y / 2, size_.z / 2);
        rect_vertexes_[3] = make_float3(-size_.x / 2, -size_.y / 2, size_.z / 2);
        break;
    case 5:
        rect_vertexes_[0] = make_float3(-size_.x / 2, -size_.y / 2, size_.z / 2);
        rect_vertexes_[1] = make_float3(size_.x / 2, -size_.y / 2, size_.z / 2);
        rect_vertexes_[2] = make_float3(size_.x / 2, -size_.y / 2, -size_.z / 2);
        rect_vertexes_[3] = make_float3(-size_.x / 2, -size_.y / 2, -size_.z / 2);
        break;
    default:
        break;
    }
}

void CudaRefinedRectangleReceiver::Cset_world_vertex()
{
    normal_ = normalize(normal_);
    //        for(float3 vertex : rect_vertexes_){
    //            vertex = global_func::rotateY(vertex, local_normal_, normal_);
    //            vertex = global_func::translate(vertex, pos_);
    //        }
    rect_vertexes_[0] = global_func::rotateY(rect_vertexes_[0], local_normal_, normal_);
    rect_vertexes_[1] = global_func::rotateY(rect_vertexes_[1], local_normal_, normal_);
    rect_vertexes_[2] = global_func::rotateY(rect_vertexes_[2], local_normal_, normal_);
    rect_vertexes_[3] = global_func::rotateY(rect_vertexes_[3], local_normal_, normal_);

    rect_vertexes_[0] = global_func::translate(rect_vertexes_[0], pos_);
    rect_vertexes_[1] = global_func::translate(rect_vertexes_[1], pos_);
    rect_vertexes_[2] = global_func::translate(rect_vertexes_[2], pos_);
    rect_vertexes_[3] = global_func::translate(rect_vertexes_[3], pos_);

    printf("%f %f %f\n", rect_vertexes_[0].x, rect_vertexes_[0].y, rect_vertexes_[0].z);
    printf("%f %f %f\n", rect_vertexes_[1].x, rect_vertexes_[1].y, rect_vertexes_[1].z);
    printf("%f %f %f\n", rect_vertexes_[2].x, rect_vertexes_[2].y, rect_vertexes_[2].z);
    printf("%f %f %f\n", rect_vertexes_[3].x, rect_vertexes_[3].y, rect_vertexes_[3].z);
}

void CudaRefinedRectangleReceiver::Cset_resolution(float pixel_per_meter_for_receiver)
{
    switch (surface_index_)
    {
    case 0:
        //????????????????这里为什么要除以2.0
        resolution_.x = int(ceil(MATH_PI / 2.0 * size_.x * float(pixel_per_meter_for_receiver)));
        resolution_.y = int(size_.y * float(pixel_per_meter_for_receiver));
        printf("       resolution %d %d \n", resolution_.x, resolution_.y);
        break;
    case 1:
        resolution_.x = int(ceil(MATH_PI / 2.0 * size_.z * float(pixel_per_meter_for_receiver)));
        resolution_.y = int(size_.y * float(pixel_per_meter_for_receiver));
        printf("       resolution %d %d \n", resolution_.x, resolution_.y);
        break;
    case 2:
        resolution_.x = int(ceil(MATH_PI / 2.0 * size_.x * float(pixel_per_meter_for_receiver)));
        resolution_.y = int(size_.y * float(pixel_per_meter_for_receiver));
        break;
    case 3:
        resolution_.x = int(ceil(MATH_PI / 2.0 * size_.z * float(pixel_per_meter_for_receiver)));
        resolution_.y = int(size_.y * float(pixel_per_meter_for_receiver));
        break;
    case 4:
        break;
    case 5:
        break;
    default:
        break;
    }
    printf("size= %f %f %f\n", size_.x, size_.y, size_.z);
    printf("surface_index %d resolution %d %d \n", surface_index_, resolution_.x, resolution_.y);
    // resolution_.x*=float(pixel_per_meter_for_receiver);
    // resolution_.y *= float(pixel_per_meter_for_receiver);
}

void CudaRefinedRectangleReceiver::setFocusCenter()
{
    focus_center_ = (rect_vertexes_[0] + rect_vertexes_[2]) / 2;
    printf("接收器中心  %f %f %f\n", focus_center_.x, focus_center_.y, focus_center_.z);
}

__device__ __host__ auto CudaRefinedRectangleReceiver::GIntersect(const float3& origin,
                                                                  const float3& dir, float& t,
                                                                  float& u, float& v) -> bool
{
    float3 offset = radius_ * local_normal_;
    float t_;
    float u_;
    float v_;

    bool isIntersectWithMargin = global_func::rayParallelogramIntersect(
        origin, dir, rect_vertexes_[0] + offset, rect_vertexes_[1] + offset,
        rect_vertexes_[3] + offset, t_, u_, v_);
    // bool isIntersectWithMargin =  global_func::rayParallelogramIntersect(origin, dir,
    // rect_vertexes_[0], rect_vertexes_[1], rect_vertexes_[3], t_, u_, v_);
    //                printf("t_ %f u %f v %f \n",t_,u_,v_);
    if (!isIntersectWithMargin)
    {
        return false;
    }

    float3 origin_ = origin + t_ * dir;
    float base = radius_ * 2 / getResolution().x;
    float times = v_ / base;
    float3 left = rect_vertexes_[0] + int(times) * base * (rect_vertexes_[3] - rect_vertexes_[0]);
    float3 right =
        rect_vertexes_[0] + (int(times) + 1) * base * (rect_vertexes_[3] - rect_vertexes_[0]);
    t = t_;
    v = v_;
    u = u_;
    if (times - int(times) >= 0.5F)
    {
        if (helper(origin_, dir, right, t, u, v, radius_, base, true))
        {
            return true;
        }
        if (helper(origin_, dir, left, t, u, v, radius_, base, false))
        {
            return true;
        }
    }
    else
    {
        if (helper(origin_, dir, left, t, u, v, radius_, base, false))
        {
            return true;
        }
        if (helper(origin_, dir, right, t, u, v, radius_, base, true))
        {
            return true;
        }
    }
    return false;
}

__device__ __host__ auto CudaRefinedRectangleReceiver::helper(const float3& origin,
                                                              const float3& dir, const float3& pos,
                                                              float& t, float& u, float& v,
                                                              float radius, float base,
                                                              bool isInRight) -> bool
{
    float2 R0 = make_float2(pos.x - origin.x, pos.z - origin.z);
    float tp = dot(R0, normalize(make_float2(dir.x, dir.z)));
    float delta = dot(R0, R0) - tp * tp;
    float R2 = radius * radius;
    if (tp < -Epsilon || delta > R2)
    {
        return false;
    }
    float t_plus = delta <= 0.0F ? radius : sqrt(R2 - delta);
    float t_ = (tp - t_plus) / length(make_float2(dir.x, dir.z));
    float3 intersect_pos = t_ * dir + origin;
    u = (intersect_pos.y - pos_.y) / size_.y + 0.5F;
    if (abs(u) < Epsilon)
        u = 0.0f;
    if (abs(u - 1.0f) < Epsilon)
    {
        //            printf("u %f\n",u);
        u = 1.0F;
    }
    if (u < 0.0F || u > 1.0F)
    {
        return false;
    }
    float2 intersect_origin_dir = make_float2(intersect_pos.x - pos.x, intersect_pos.z - pos.z);
    intersect_origin_dir = normalize(intersect_origin_dir);
    float v_ = acosf(intersect_origin_dir.x) / (2 * M_PI);
    float2 vect;
    if (isInRight == true)
        vect = make_float2(rect_vertexes_[0].x - rect_vertexes_[3].x,
                           rect_vertexes_[0].z - rect_vertexes_[3].z);
    else
        vect = make_float2(rect_vertexes_[3].x - rect_vertexes_[0].x,
                           rect_vertexes_[3].z - rect_vertexes_[0].z);
    vect = normalize(vect);
    float v0 = acosf(vect.x) / (2 * M_PI);
    v_ -= v0;
    if (isInRight == true)
    {
        v += 0.5F * base + v_ * base;
    }
    else
    {
        v += 0.5F * base - v_ * base;
    }

    t += t_;
    return true;
}
